#include "GraphLoader.hpp"

#include <boost/property_tree/xml_parser.hpp>
#include <boost/foreach.hpp>
#include <iostream>
#include <set>
#include <string>
#include <boost/algorithm/string.hpp>

// Used for linux file IO:
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sstream>
#include <fstream>
#include <iostream>

#include <boost/lexical_cast.hpp>

using namespace std;
using namespace boost;
using namespace TopologicalLearner;

//----------------------------------------------------------------
pair<string,string> GraphLoader::GetNameTypeAttr
        (const property_tree::ptree::value_type v) const
{
    using boost::property_tree::ptree;
    string sName, sType;

    BOOST_FOREACH(const ptree::value_type &i,
    v.second.get_child("<xmlattr>"))
    {
        if( ((string)i.first.data()).compare("name") == 0 )
            sName = i.second.data();
        else
            sType = i.second.data();
    }

    return make_pair(sName, sType);
}
//----------------------------------------------------------------
pair<bool, Graph> GraphLoader::LoadSingleGraph(string sFilename, bool bUnique, std::string rootNodeName)
{
    // Create an empty property tree object
    using boost::property_tree::ptree;
    ptree pt;

    // This maps from name -> type
    typedef map< string, string > map_t;
    map_t nameData;

    // This is used for collecting meta-data, we should only count
    // each category once per graph. This is used to check that.
    map<string, bool> TypeCountedMap;

    // If we do not have unique node labelings, then we must keep track of
    // what each vertex corresponds to which name in order to
    // add the edges properly.
    map<string, Vertex> kNameToVertex;

    // A temporary storage for the Graph being created
    Graph kTmpG;


    // A local map for this specific graph, from type->vertex (in local graph kTmpG)
    map<string, Vertex> TypeVertexMap;

    // Load the XML file into the property tree. If reading fails
    // (cannot open file, parse error), an exception is thrown.
    read_xml(sFilename, pt);
    //get(graph_name, kTmpG) = sFilename;

    size_t found;
    found=sFilename.find_last_of("//");


    //sFilename.find_last_of("//")
    set_property(kTmpG, boost::graph_name, sFilename.substr(found+1));

    // We shall first map the different names to types, create a map from Name -> Type
    // since we're not interested in specific rooms, we want the general topology.

    // Go through and pick out the "space" categories from the file
    BOOST_FOREACH(ptree::value_type &v,
            pt.get_child(rootNodeName.c_str()))
    {
            if( ((string)v.first.data()).compare("space") == 0 )
            {
                // This is a "room", ie a semantic category.
                // Get the xmlattribute type.
                pair<string,string> pTmp = GetNameTypeAttr(v);


               pair<set<string>::iterator,bool> kRet = m_Types.insert(pTmp.second);
               if( kRet.second )
               {
                    // Record which vertex this name belongs to.
                    //m_TypeVertexMap[pTmp.second] = pNew;

                    m_TypesID[pTmp.second] = m_iIDCounter;
                    m_IDTypes[m_iIDCounter] = pTmp.second;
                    m_iIDCounter++;

                    //put(vertex_name, m_MetaGraph, pNew, m_TypesID[pTmp.second]);
                    //TypeCountedMap[pTmp.second] = true;
               }

                bool b1 = true;

                // So if we find a '.' for this name, we should only add if we haven't added it already
                // since we want to lump these together
                if( pTmp.first.find('.') != string::npos )
                {
                    pTmp.first = pTmp.first.substr(0, pTmp.first.find('.'));
                    if( kNameToVertex.find(pTmp.first) != kNameToVertex.end() )
                        b1 = false;
                }

                // Make sure that we get unique node labelings.
                if( (!bUnique && b1) || TypeCountedMap.find(pTmp.second) == TypeCountedMap.end() )
                {

                    // Add the new vertex to the local graph.
                    Vertex pNew;
                    pNew = add_vertex(kTmpG);


                    put(vertex_name, kTmpG, pNew, m_TypesID[pTmp.second]);
                    TypeVertexMap[pTmp.second] = pNew;

                    // Point this name to the vertex added (if we do not have unique node labels)
                    kNameToVertex[pTmp.first] = pNew;


                    TypeCountedMap[pTmp.second] = true;
                }
               //kTmpG

               // Associate this name with the category.
               nameData[pTmp.first] = pTmp.second;
            }
        }
    static int iTrans = 0;

    // Now construct the graph, go through all the rooms again
    BOOST_FOREACH(ptree::value_type &v,
            pt.get_child(rootNodeName.c_str()))
    {
            if( ((string)v.first.data()).compare("space") == 0 )
            {
                pair<string,string> pOrigin = GetNameTypeAttr(v);
                // Get the name of the origin (same for both unique/non-unique)
                string sOrigin = pOrigin.first;//pOrigin.second;

                if( sOrigin.find('.') != string::npos )
                    sOrigin = sOrigin.substr(0, sOrigin.find('.'));

                Vertex vOrigin = kNameToVertex[sOrigin];
                // pOrigin.second has the vertex from which we are connecting


              //  bool bTransAdd = false;
                BOOST_FOREACH(ptree::value_type &i,
                        v.second)
                {
                    // Compare if its portal

                    if( ((string)i.first.data()).compare("portal") == 0 )
                    {


                        BOOST_FOREACH(ptree::value_type &j,
                                i.second.get_child("<xmlattr>"))
                                {
                                    /*
                                    if( ((string)j.first.data()).compare("type") == 0 )
                                    {
                                        if( ((string)j.second.data()).compare("TRANSITION") == 0 && pOrigin.second.compare("CORR") == 0 )
                                            //iTrans++;
                                            bTransAdd = true;
                                    }*/
                                    if( ((string)j.first.data()).compare("target") == 0 )
                                    {
                                        //string sTarget = ((string)j.second.data());
                                        // Now ensure that this specific room is in our mapping
                                        // from specific rooms -> types. (this is due to the data format,
                                        // some of the sources does not seem to be valid rooms)
                                        // If so, add an edge to the topology and update meta-topology.
                                        //map<string,string>::const_iterator itTmp =
                                        //    nameData.find(((string)j.second.data()));

                                        // Remove all the .a .b etc
                                        string sTarget = (string)j.second.data();
                                       if( sTarget.find('.') != string::npos )
                                            sTarget = sTarget.substr(0, sTarget.find('.'));

                                        map<string, Vertex>::const_iterator ivTarget =
                                            kNameToVertex.find(sTarget);
                                        //cout << "Connecting " << sOrigin << " to " << sTarget << endl;
                        //12
                                        //if( itTmp != nameData.end()
                                        if( ivTarget != kNameToVertex.end() )
                                        //    && m_Types.find(itTmp->second) != m_Types.end() )
                                        {
                                                //string sTarget = itTmp->second;
                                                Vertex vTarget = ivTarget->second;

                                                //pair<Edge, bool> kRet2 = edge(TypeVertexMap[sOrigin], TypeVertexMap[sTarget], kTmpG);
                                                pair<Edge, bool> kRet2 = edge(vOrigin, vTarget, kTmpG);

                                                // Don't allow self-edges
                                                //if( !kRet2.second && TypeVertexMap[sOrigin] != TypeVertexMap[sTarget] )
                                                if( !kRet2.second && vTarget != vOrigin )
                                                // Add edge in local graph
                                                add_edge(vTarget,vOrigin, kTmpG);
                                                // This is a valid connection, add it.
                                        }
                                    }
                                }

                    }


                }
                //if( bTransAdd && in_degree(vOrigin, kTmpG) <= 2  )
               //     iTrans++;
            }
    }

    //m_Graphs.push_back(kTmpG);
   // cout << iTrans << endl;
    return pair<bool, Graph>(true, kTmpG);

}
//----------------------------------------------------------------
int GraphLoader::CountFilesInDir(string sDir) const
{
    DIR *dp;
    stringstream fLoad;
    struct dirent *dirp;

    if((dp  = opendir(sDir.c_str())) == NULL)
    {   cout << "Error(" << errno << ") opening " << sDir << endl;
        return 0;
    }
    int iX = 0;
    while ((dirp = readdir(dp)) != NULL ){
        if( strlen(dirp->d_name) != 0 && dirp->d_name[0] != '.' )
        {
            fLoad << sDir << "/" << dirp->d_name;
            struct stat statTmp;
            stat(fLoad.str().c_str(), &statTmp);

            // Check if it is a file or directory
            if( !S_ISDIR(statTmp.st_mode) )
                iX++;
            else
            {   iX += CountFilesInDir(string(fLoad.str()));
                fLoad.str(""); }
        }
    }
    return iX;
}
//----------------------------------------------------------------
int GraphLoader::LoadAllGraphs(string sDir, vector<Graph>& vResult, int iLimit, bool bUnique)
{
    DIR *dp;
    stringstream fLoad;
    struct dirent *dirp;


    if((dp  = opendir(sDir.c_str())) == NULL)
    {
        cout << "Error(" << errno << ") opening " << sDir << endl;
        return 0;
    }

    int iCounter = 0;

    //int iFiles = CountFilesInDir(sDir);
    //vResult.reserve(iFiles);

    // Load all the files (except current directory(.) and previous dir(..))
    while ((dirp = readdir(dp)) != NULL )
    {
        //cout << dirp->d_name << endl;
        string str(dirp->d_name);
        if( strlen(dirp->d_name) != 0 && dirp->d_name[0] != '.' && str.compare(str.length() - 4, 4, ".xml") == 0)
        {
            fLoad << sDir << "/" << dirp->d_name;

            struct stat statTmp;
            stat(fLoad.str().c_str(), &statTmp);

            // Check if it is a file or directory
            if( !S_ISDIR(statTmp.st_mode) )
            {
                cout << "Loading file " << fLoad.str() << endl;

                pair<bool, Graph> kRes = LoadSingleGraph(string(fLoad.str()), bUnique);
                if( kRes.first )
                {
                    vResult.push_back(kRes.second);
                    size_t found;

                    string sFilename = fLoad.str();
                    found=sFilename.find_last_of("//");
                    sFilename = sFilename.substr(found+1);

                    m_GraphFilenames.push_back(string(sFilename));
                    iCounter++;
                }
                fLoad.str("");
            }
            else
            {
                if(dirp->d_name[0] == '.'){
                    cout << "Loading directory " << fLoad.str() << endl;
                    iCounter += LoadAllGraphs(string(fLoad.str()), vResult, iLimit);
                    fLoad.str("");
                }
            }
        }
        if( iLimit != -1 && iCounter >= iLimit )
            return iCounter;
    }


    closedir(dp);

    return iCounter;
}
//----------------------------------------------------------------
void GraphLoader::SaveGSpanData(string sFilename, const vector<Graph>& kVec) const
{

    ofstream out(sFilename.c_str(), ios_base::out);

    for(unsigned int i=0; i<kVec.size(); i++)
    {
        out << "t # " << i << endl;
        const Graph& G = kVec[i];


        Iter_t vi, vi_end;
        int j = 0;
        // For each vertex
        for (tie(vi, vi_end) = vertices(G); vi != vi_end; ++vi)
        {
            out << "v " << j << " " << get(vertex_name, G, *vi) << endl;
            j++;
        }

        graph_traits<Graph>::edge_iterator firstE, lastE;
        // For each edge
        for (tie(firstE, lastE) = edges(G); firstE != lastE; ++firstE)
        {
            out << "e " << boost::source(*firstE, G) << " "
                         << boost::target(*firstE, G) << " 1" << endl;

        }
    }



    out.close();
}
//----------------------------------------------------------------
vector<FrequentSubgraph> GraphLoader::LoadGSpanData(string sFilename)
{

    ifstream in(sFilename.c_str());

    vector<FrequentSubgraph> kVecTmp;


    string sTmp;
    while( !in.eof() )
    {

        FrequentSubgraph gTmp;
        Graph& G = gTmp.G;

        std::vector<std::string> strs;

        getline(in, sTmp, '\n');
        boost::split(strs, sTmp, boost::is_any_of("\t "));


        while( strs.size() != 0 && strs[0].size() != 0 )
        {

            if( strs[0].compare("t") == 0 )
            {
                //t # 0 * 272
                //gTmp.iSupport = lexical_cast<int>(strs[4]);

            }

            // Vertex found
            if( strs[0].compare("v") == 0 )
            {
                Vertex vNew = add_vertex(G);
                int iID = lexical_cast<int>(strs[2]);

                //G[vNew].sName = m_IDTypes[iID];
                put(vertex_name, G, vNew, iID);

            }

            if( strs[0].compare("e") == 0 )
            {
                // Add an edge
                add_edge(lexical_cast<Vertex>(strs[1]), lexical_cast<Vertex>(strs[2]), G);
            }

            if( strs[0].compare("x") == 0 )
            {
                for(unsigned int l = 1; l<strs.size()-1; l++)
                {
                    gTmp.vGraphIDs.push_back(lexical_cast<int>(strs[l]));
                }
            }

            getline(in, sTmp, '\n');
            boost::split(strs, sTmp, boost::is_any_of("\t "));

        }
        gTmp.G = G;
        if( gTmp.vGraphIDs.size() != 0 )
            kVecTmp.push_back(gTmp);
    }

    return kVecTmp;
}
//----------------------------------------------------------------
